/*
 *  Jiki - a small lightweight wiki engine.
 *
 *  Copyright 2008 Peter Halbmayer <phalbmayer@gmail.com
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 */

package at.pmsa.jiki.core;

import java.io.CharArrayWriter;
import java.io.File;
import java.util.Date;

import android.util.Log;
import at.pmsa.jiki.conn.http.HTTPRequest;
import at.pmsa.jiki.conn.http.HTTPResponse;
import at.pmsa.jiki.util.DBMan;
import at.pmsa.jiki.util.IJConstants;

public class JikiGenerator implements IJConstants{
	
	HTTPRequest request;
	DBMan dbMan;
	
	public JikiGenerator(HTTPRequest r, DBMan m){
		this.request = r;
//		pageName = "JHome";
		this.dbMan = m;
	}
	
	public void setHTTPRequest(HTTPRequest r){
		request = r;
	}

	public HTTPResponse handleRequest(){
		
		String command = "";
		String wikitext = "";
		String pageName = "";
		
		StringBuffer htmlHead = new StringBuffer();
		StringBuffer htmlBody = new StringBuffer();
		StringBuffer htmlFoot = new StringBuffer();
		
//		appendHeader(htmlHead);
		appendFooter(htmlFoot);
		
		HTTPResponse res = new HTTPResponse(HTTP_OK, null);
		
		String httpCmd = request.getCommand();
		String path = request.getPath();
		Log.v("JikiGenerator", "PATH: " + path);
		Log.v("JikiGenerator", "HTTP command: " + request.getCommand());
		
		if(path.equals("/")){
			//show start page
			path = "JHome";
			request.setPath("JHome");
		}
		//else strip off beginning slash and compare rest of path
		else{
			path = path.substring(1);
		}
		
		//extract wiki command
		if(path.contains("?")){
			command = path.substring(path.indexOf("?") + 1, path.length());
			path = path.substring(0, path.indexOf("?"));
		}
		pageName = path;
		
		Log.v("JikiGenerator", "COMMAND: " + command);
		Log.v("JikiGenerator", "PAGENAME: " + pageName);
		
		//only arises if new page created explicitly (over create dialog;
		//path == "Create" then) or if path does not equal "Create" then
		//the content is a newly edited page; must be written to file system
		if(httpCmd.equals("POST")){
			
			if(pageName.equals("Create")){
				Log.v("JikiGenerator", "Processing Create POST request");
				
				//read and parse content of request
				String[] parts = request.getContent().trim().split("=");
				
				if(parts[0].trim().equalsIgnoreCase("title")){					
					pageName = parts[1];
					
					if(dbMan.fileExists(pageName))
						wikitext = dbMan.readFile(pageName);
					
					appendHeader(htmlHead, pageName);
					res.addContent(htmlHead.toString());
					
					appendEditPage(htmlBody, parts[1].trim(), wikitext);
					res.addContent(htmlBody.toString());
					
					res.addContent(htmlFoot.toString());
					return res;
				}
			}
			else{
				wikitext = request.getContent().trim();
				wikitext = wikitext.trim().substring(wikitext.indexOf('=') + 1);
				dbMan.writeFile(pageName, wikitext);
			}
		}
		
		//depending on path a decision is made which response is returned
		//first strip off leading slash
		if(pageName.equals("styles.css")){
			res.setContentType("text/css");
			res.addContent(STYLESHEET);
			return res;
		}
		//BEWARE: depends on browser how favicon is read it seems
		//works in android browser but not in konqueror for example
		else if(pageName.equals("favicon.ico")){
			CharArrayWriter writer = new CharArrayWriter();
			for(int t = 0; t < FaviconDataLen; t++){
				writer.append(FaviconData[t]);
			}
			System.out.println(writer.toString());
			res.addContent(writer.toString());
			return res;
		}
		else if(pageName.equals("Filesystem")){
			appendHeader(htmlHead, pageName);
			res.addContent(htmlHead.toString());
			appendFileRoot(htmlBody);
			res.addContent(htmlBody.toString());
			res.addContent(htmlFoot.toString());
			return res;
		}
		else if(pageName.equals("Filelist")){
			appendHeader(htmlHead, pageName);
			res.addContent(htmlHead.toString());
			appendPages(htmlBody, "ALL");
			res.addContent(htmlBody.toString());
			res.addContent(htmlFoot.toString());
			return res;
		}
		else if(pageName.equals("Changes")){
			//show list of recent changes; or only files where recently something has changed
			//algorithm herein may also be used for synchronization
		}
		else if(pageName.equals("Search")){
			//retrieve keyword from request
			String cmdParts[] = command.split("=");

			appendHeader(htmlHead, pageName);
			res.addContent(htmlHead.toString());
			appendPages(htmlBody, cmdParts[1]);
			res.addContent(htmlBody.toString());
			res.addContent(htmlFoot.toString());
			return res;
			
		}
		else if(pageName.equals("Create")){
			//TODO: return page name in response
			if(request.getCommand().equals("GET")){
				Log.v("JikiGenerator", "Processing GET request");

				appendHeader(htmlHead, pageName);
				res.addContent(htmlHead.toString());
				appendCreatePage(htmlBody);
				res.addContent(htmlBody.toString());
				res.addContent(htmlFoot.toString());
				return res;
			}
		}
		else{
			//try to load path page
			//if not possible and page name valid return edit page of new page name
			//else return some error message or return a sensible default
			wikitext = dbMan.readFile(pageName);
			if(wikitext == null){
				//return empty edit page with pageName as page title
				if(pageName != null){
					appendEditPage(htmlBody, pageName, "");
				}
				else{
					//else return to home page
					//TODO: return modified home page if exists
					appendData(htmlBody, HOMETEXT);
				}
			}
			else{
				if(command.equals("edit")){
					if(request.getCommand().equals("GET")){
						appendEditPage(htmlBody, pageName, wikitext);
					}
					else{
						//send back some default server error response
						//has to be made more generic as now wiki header
						//and footer will be sent
						return new HTTPResponse(HTTP_BAD_METHOD, null);
					}
				}
				else{
					//return wiki processed html output stored in file
					appendData(htmlBody, wikitext);
				}
			}
		}
		
		//for the few cases that fall through
		appendHeader(htmlHead, pageName);
		res.addContent(htmlHead.toString());
		res.addContent(htmlBody.toString());
		res.addContent(htmlFoot.toString());
		return res;
	}
	
	private void appendHeader(StringBuffer head, String page){
		  String htmlHeader = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 " +
		  		"Transitional//EN\" " +
		  		"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" +
		  		"<html xmlns='http://www.w3.org/1999/xhtml'>\n" +
		  		"<head>\n" +
		  		"<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />\n" +
		  		"<link rel='SHORTCUT ICON' href='/favicon.ico' />\n" +
		  		"<link media='all' href='/styles.css' rel='stylesheet' type='text/css' />\n" +
		  		"<title>" +
		  		page + // here title of page must be added; gets displayed in window decoration
		  		"</title>\n" +
		  		"</head>\n" +
		  		"<body>\n";
		  
		  	//header may look different when page is in edit state
			String pageHeader = "" +
			"<div id='header'>\n" +
			"<form method='get' action='/Search'>\n" +
//			"<table border='0' width='100%%'>\n" +
			"<table border='0'>\n" +
			"<tr>\n" +
			"<td align='left' ><strong>" +
			page + //name in wiki header
			"</strong> " +
			"<a href='" +
			page + //page name that is sent when page is edited
			"?edit' title='Edit this wiki page contents. [alt-j]' accesskey='j'>Edit</a>" +
			"</td>\n" +
			"<td align='right'>\n" +
			"<a href='/JHome' title='Visit Wiki home page. [alt-z]' accesskey='z'>Home</a> | <br> \n" +
			"<a href='/Filesystem' title='Show filesystem listing. [alt-l]' accesskey='z'>List Filesystem</a> | <br> \n" +
			"<a href='/Filelist' title='List recent changes in the wiki. [alt-r]' accesskey='r' >Filelist</a> | <br> \n" +
			"<a href='/Changes' title='List recent changes in the wiki. [alt-r]' accesskey='r' >Changes</a> | <br> \n" +
			"<a href='/Create' title='Create a new wiki page by title. [alt-c]' accesskey='c'>New</a> | <br> \n" +
			"<a href='/JHelp' title='Get help on wiki usage and formatting.'>Help</a> | <br> \n" +
			"<input type='text' name='expr' size='15' title='Enter text to search for and press return.' /> <br>" +
			"</td>\n" +
			"</tr>\n" +
			"</table>\n" +
			"</form>\n" +
			"</div>\n" +
			"<div id='wikidata'>\n";
			
			head.append(htmlHeader);
			head.append(pageHeader);
	}
	
	private void appendFileRoot(StringBuffer body){
		
		File fileRoot = null;
		
		fileRoot = new File("/");
		if(!fileRoot.exists()){
			body.append("Unable to find file root: " + fileRoot);
			return;
		}
		System.out.println("root: " + fileRoot);
	
		body.append("<title>Directory listing</title><p>\n");
		body.append("<a href=\"..\">Parent Directory</a><br>\n");

		String[] list = fileRoot.list();
		for(int i = 0; list != null && i < list.length; i++){
			File f = new File(fileRoot, list[i]);
			if(f.isDirectory()){
				//TODO: (optional) append certain tags for file and directory links
				//and parse for these links to enable thorough file browsing
				//add special tags for files depending on their type
				//extension: is it possible to start applications
				//with the corresponding files determined by
				//MIME type
				body.append("<a href=\"file:///" + list[i] + "/\">" + list[i] + "/</a><br>");
			}
			else{
				body.append("<a href=\"file:///" + list[i] + "\">" + list[i] + "</a><br>");
			}
		}
		body.append("<p><hr><br><i>" + (new Date()) + "</i>");
	}
	
	private void appendFooter(StringBuffer foot){
		foot.append("</div><div id='footer'>Jiki, Version: " + VERSION +
				"</div>\n" + "</body>\n</html>\n");
	}
	
	private void appendEditPage(StringBuffer body, String pageName, String pageText){
		
//		System.out.println("Appending edit page: " + pageName + "\ntext: " + pageText);
		String EDITFORM = "<form method=POST action=" +
					pageName +
					" name='editform'>\n" +
//					"<textarea name='wikitext' rows='20' cols='80' wrap='virtual'>" +
					"<textarea name='wikitext' wrap='virtual'>" +
					pageText +
					"</textarea>\n" +
					"<p><input type='submit' value='Save' title='[alt-k]' accesskey='k'></p>\n" +
					"</form>\n" +
					"<script language='JavaScript'>\n" +
					"<!--\n" +
					"/* Give the textarea focus ...less mouse usage but may annoy people ?*/ \n" +
					"document.editform.wikitext.focus()\n" +
					"document.editform.wikitext.scrollTop = document.editform.wikitext.scrollHeight;\n" +
					"//-->\n" +
					"</script>\n";
		body.append(EDITFORM);
	}

	private void appendCreatePage(StringBuffer body){
		String CREATEFORM ="<form method=POST action='/Create' name='createform'>\n" +
		"<input type='text' name='title' />\n" +
		"<p><input type='submit' value='Create' /></p>\n" +
		"</form>\n";
		body.append(CREATEFORM);
	}
	
	private void appendPages(StringBuffer body, String key){
		
		String[] keys = null;
		//parse multi expression search word
		//space is given by  '+' character -> has
		//to be escaped whether by \\+ or [+]
		//last version see regexes
		if(key.contains("+")){
			keys = key.split("[+]");
		}
		
		String[] fileNames = dbMan.getAllFileNames();
		
		//only return a list with a link for every page (except home)
		if(key.equals("ALL")){
			for(int i = 0; i < fileNames.length; i++){
				String s = fileNames[i];
				if(!s.equals("JHome") && !s.equals("JHelp") && !s.equals("jiki.properties") &&
						!s.equals("jiki.log") && !s.contains("~"))
					body.append("<a href=\"" + fileNames[i]+"\">" + fileNames[i] + "</a><br>\r\n");
			}
		}
		//search pages for passed keyword and return only links to
		//pages that contain keyword
		else{
			int p = 0;
			int c = 0;
			for(int i = 0; i < fileNames.length; i++){
				String searchStr = dbMan.readFile(fileNames[i]);
				
				if(keys != null){
					for(int j = 0; j < keys.length; j++){
						if(searchStr.contains(keys[j])){
							c++;
						}
					}
					if(c > 0){
						body.append("<a href=\"" + fileNames[i] + "\">" + fileNames[i] + "</a><br>\r\n");
						p++;
					}
					c = 0;
				}
				else{
					if(searchStr.contains(key)){
						body.append("<a href=\"" + fileNames[i] + "\">" + fileNames[i] + "</a><br>\r\n");
						p++;
					}
				}
			}
			if(p == 0)
				body.append("No pages with search expression '" + key + "' found!");
		}
	}
	
	//actual generation of HTML output by parsing wiki marked up text
	private void appendData(StringBuffer body, String data){

		String line;
		//contains the html formatted wiki data
		StringBuffer htmlData = new StringBuffer();
		
		JikiScanner.init(data);
		
		//NOTE: all elements must start in a new line except
		//links, bold, underline, strikethrough, italics
		
		//main loop; process on a line base, read until no more lines available
		while((line = JikiScanner.nextLine()) != null){
			
			Log.v("JikiGenerator", "line: " + line);
			checkLine(line, htmlData);
		}
		htmlData.append("\n</p>\n");
		
		body.append(htmlData.toString());
	}
	
	static int openUL = 0;
	static int openOL = 0;
	static int headerLevel = 0;
	static boolean plainTextOpen = false;
	static boolean tableOpen = false;
	static boolean inParagraph = false;
	
	private void checkLine(String line, StringBuffer output){
		
		boolean atStartOfLine = true;
		boolean openListItem = false;
		boolean boldOpen = false;
		boolean italOpen = false;
		boolean underOpen = false;
		boolean strikeOpen = false;
		
		int j, k, l;
		
		if(headerLevel > 0){
			output.append("</h" + headerLevel + ">\n");
			headerLevel = 0;
		}
		
		if(tableOpen && !line.startsWith("|")){
			tableOpen = false;
			output.append("\n</table>\n");
		}
		
		if(plainTextOpen && !line.startsWith(" ")){
			if(line.length() == 0){
				output.append("\n");
				return;
			}
			else{
				output.append("</pre>\n");
				plainTextOpen = false;
			}
		}
		
		if(line.length() == 0 && !inParagraph){
			output.append("\n<p>\n");
			inParagraph = true;
		}
		else if(inParagraph){
			output.append("\n</p><p>\n");
			inParagraph = false;
		}
		
		if(openUL > 0 && !line.startsWith("*")){
			while(openUL > 0){
				output.append("</ul>\n");
				openUL--;
			}
		}
		
		if(openOL > 0 && !line.startsWith("#")){
			while(openOL > 0){
				output.append("</ol>\n");
				openOL--;
			}
		}
		
		for(int i = 0; i < line.length(); i++){
			
			switch(line.charAt(i)){
			
			case ' ':
				if(atStartOfLine){
					if(!plainTextOpen){
						output.append("<pre>\n" + line.substring(i + 1) + "\n");
						plainTextOpen = true;
						return;
					}
					else{
						atStartOfLine = false;
						continue;
					}
				}
				else
					output.append(line.charAt(i));
				break;
				
			//HEADER
			case '=':
				if(atStartOfLine){
					j = i + 1;
					while(line.charAt(j) == '='){
						j++;
					}
					headerLevel = j;
					output.append("<h" + j + ">");
				}
				else if(headerLevel == 0)
					output.append("=");
				break;
				
			//HORIZONTAL RULE OR STRIKETHROUGH
			case '-':
				k = i + 1;
				
				if(k >= line.length() && strikeOpen){
					strikeOpen = false;
					output.append("</del>");
				}
				else if(k < line.length() && line.charAt(k) == ' ' && strikeOpen){
					strikeOpen = false;
					output.append("</del>");
				}
				
				if(atStartOfLine && !Character.isLetter(line.charAt(k))){
					try{
						while(line.charAt(k) == '-'){
							k++;
							if(k >= 4)
								break;
						}
					}
					catch(StringIndexOutOfBoundsException e){}
					
					if(k < 2){
						strikeOpen = true;
						output.append("<del>");
					}	
					//four '-' found at start of line
					else if(k >= 4){
						output.append("\n<hr>\n");
						i += 4;
					}
					else
						output.append(line.charAt(i));
				}
				else if(Character.isLetter(line.charAt(i -1)) && Character.isLetter(line.charAt(k)))
					output.append(line.charAt(i));
				else{
					if(strikeOpen){
						strikeOpen = false;
						output.append("</del>");
					}
					else{
						strikeOpen = true;
						output.append("<del>");
					}
				}
				break;
				
			//UNDERLINE
			case '_':
				if(underOpen){
					output.append("</u>");// + line.substring(i + 1, k) + "</u>");
					underOpen = false;
				}
				else{
					output.append("<u>");
					underOpen = true;
				}				
				break;
				
			//ITALICS
			case '/':
				if(italOpen){
					output.append("</i>");// + line.substring(i + 1, k) + "</u>");
					italOpen = false;
				}
				else{
					output.append("<i>");
					italOpen = true;
				}				
				break;
				
			//BOLD or ULIST
			case '*':
				k = i + 1;
				
				//ULIST
				//another condition has to be revised to ensure
				//that it really is ULIST and not start of bold
				//formatting for following text
				if(atStartOfLine && !Character.isLetter(line.charAt(k)) && !openListItem){
					//k gives the number of star symbols at start of line
					try{
						while(line.charAt(k) == '*')
							k++;
					}
					catch(StringIndexOutOfBoundsException e){
//						e.printStackTrace();
					}
					//line only consists of stars
					if(k >= line.length()){
						output.append(line);
						return;
					}
					else if(line.charAt(k) == ' '){
						//list starts in this line
						if(k == 1 && openUL == 0){
							output.append("\n<ul>\n");
							output.append("<li>");
							openListItem = true;
							openUL++;
						}
						//list already started in a previous line
						else if(k > 1 && openUL > 0 && k > openUL){
							int dist = k - openUL;
							int u = dist;
							while(u-- > 0){
								output.append("<ul>\n");
								openUL++;
							}
							u = dist;
							output.append("<li>");
							openListItem = true;
							
						}
						else if(k < openUL){
							output.append("</ul>\n");
							output.append("<li>");
							openListItem = true;
							openUL--;
						}
						else if(k == openUL){
							output.append("<li>");
							openListItem = true;
						}
						else{
							Log.d("JikiGenerator", "Something strange is happening at ulist" + line.charAt(i));
						}
					}
					i = k;
				}
				//BOLD
				else{
					if(boldOpen){
						boldOpen = false;
						output.append("</b>");
					}
					else{
						boldOpen = true;
						output.append("<b>");
					}
				}
				//TODO: prepare for case when '*' symbol itself has to be printed out
//				else{
//					output.append(line.charAt(i));
//				}
				break;
				
			//OLIST
			case '#':
				if(atStartOfLine){
					//k gives the number of star symbols at start of line
					k = i + 1;
					try{
						while(line.charAt(k) == '#')
							k++;
					}
					catch(StringIndexOutOfBoundsException e){
						e.printStackTrace();
					}
					if(line.charAt(k) == ' '){
						//list starts in this line
						if(k == 1 && openOL == 0){
							Log.v("JikiGenerator", "Starting new olist");
							output.append("<ol>\n");
							output.append("<li>");
							openListItem = true;
							openOL++;
						}
						//list already started in a previous line
						else if(k > 1 && openOL > 0 && k > openOL){
							int dist = k - openOL;
							int u = dist;
							while(u-- > 0){
								output.append("<ol>\n");
								openOL++;
							}
							output.append("<li>");
							openListItem = true;
							
						}
						else if(k < openOL){
							output.append("</ol>\n");
							output.append("<li>");
							openListItem = true;
							openOL--;
						}
						else if(k == openOL){
							output.append("<li>");
							openListItem = true;
						}
						else{
							Log.e("JikiGenerator", "Something strange is happening at olist" + line.charAt(i));
						}
					}
				}
				else{
					//# symbol has single semantics
					output.append(line.charAt(i));
				}
				break;
				
			//TABLE
			case '|':
				if(atStartOfLine){
					if(!tableOpen){
						tableOpen = true;
						output.append("\n<table class='wikitable' cellspacing='0' cellpadding='4'>\n");
					}
					
					output.append("\n<tr>");
					String tableData[] = line.split(" | ");
					
					for(int x = 0; x < tableData.length; x++){
						if(tableData[x].trim().equals("|"))
							continue;
						output.append("<td>" + tableData[x] + "</td>\n");
					}
					output.append("</tr>\n");
					return;
				}
				else
					output.append(line.charAt(i));
				break;
				
			//LINKS
				
			//for http://
			case 'h':
				l = i + 1;
				if(line.charAt(l) != 't')
					output.append(line.charAt(i));
				else{
					if(line.substring(i).startsWith("http://")){
						try{
							while(line.charAt(l) != ' ' && line.charAt(l) != ',')
								l++;
						}
						catch(StringIndexOutOfBoundsException e){}
						if(l > i + 1){
							String link = line.substring(i, l);
							if(link.trim().endsWith(".gif") ||
									link.trim().endsWith(".png") ||
									link.trim().endsWith(".jpg") ||
									link.trim().endsWith(".jpeg")){
								output.append("<img src='" + link.trim() + "' border='0'>");
							}
							else{
								output.append("<a title='WWW Link' href='" + link + "'>" + link + "</a>");
							}
							i = l;
						}
						else
							output.append(line.charAt(i));
					}
					else
						output.append(line.charAt(i));
				}
				break;
				
			//possible CamelCase
			case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I':
			case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
			case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':

				int cCase = 0;
				l = i + 1;
				try{
					while(line.charAt(l) != ' '){
						if((line.charAt(l) == '\r' && line.charAt(l + 1) == '\n') || line.charAt(l) == '\n')
							break;
						if(Character.isUpperCase(line.charAt(l)))
							cCase++;
						l++;
					}
				}
				catch(StringIndexOutOfBoundsException e){
					e.printStackTrace();
				}
				
				if(cCase > 0){
					String cCaseLink = line.substring(i, l);
					output.append("<a href='http://" + request.getHost() + ":" + request.getPort() + "/" + cCaseLink +
							"'>" + cCaseLink + "</a> ");
					i = l;
				}
				else
					output.append(line.charAt(i));
				break;
				
			case '[':
				l = i + 1;
				try{
					while(line.charAt(l) != ']'){
						l++;
					}
					String bracedLink = line.substring(i + 1, l);
					String linkParts[] = bracedLink.split(" ");

					// - forced link: [name]
					if(linkParts.length == 1){
						output.append("<a href='http://" + request.getHost() + ":" + request.getPort() +
								"/" + linkParts[0].trim() + "'>" + linkParts[0].trim() + "</a>");
						i = l;
					}
					// - named link: [url name]
					//url can also link to a local page
					else if(linkParts.length == 2){
						
						if(linkParts[0].trim().startsWith("http://")){
							if(linkParts[0].trim().endsWith(".gif") ||
									linkParts[0].trim().endsWith(".png") ||
									linkParts[0].trim().endsWith(".jpg") ||
									linkParts[0].trim().endsWith(".jpeg")){
								output.append("<a href='" + linkParts[1].trim() +
										"'><img src='" + linkParts[0].trim() + "' border='0'></a>");
							}
							else{
								output.append("<a title='WWW Link' href='" + linkParts[0].trim() + 
										"'>" + linkParts[1].trim() + "</a>");
							}
						}
						else{
							output.append("<a href='" + linkParts[0].trim() +
									"'>" +linkParts[1].trim() + "</a>");
						}
						i = l;
					}
				}
				//if this exception is found, the closing brace is not present therefore
				//only write out opening brace and go on with next symbol
				catch(StringIndexOutOfBoundsException e){
					e.printStackTrace();
					output.append(line.charAt(i));
				}
				break;
				
			//skip interpreting of links and write out in cleartext
			//(without the escaping symbol '!')
			case '!':
				l = i + 1;
				try{
					
					while(line.charAt(l) != ' '){
						l++;
					}
				}
				catch(StringIndexOutOfBoundsException e){}
				if(l == i + 1){
					output.append("!");
				}
				else{
//					if(l < line.length())
//						output.append(line.substring(i + 1, l));
//					else
//						output.append(line.substring(i + 1, l - 1));
					output.append(line.substring(i + 1, l));
					output.append(" ");
					i = l;
				}
				break;
				
			case '<':
				output.append("&lt;");
				break;
			
			case '>':
				output.append("&gt;");
				break;
			
			case '&':
				output.append("&amp;");
				break;
			
			case '\"':
				output.append("&quot");
				break;
				
			default:
				output.append(line.charAt(i));
				break;
			}
			atStartOfLine = false;
		}
		output.append("\n");
		
		//safety measure so that formatting within
		//line would not affect following lines
		if(openListItem){
			output.append("</li>\n");
			openListItem = false;
		}
		if(boldOpen){
			output.append("</b>\n");
			boldOpen = false;
		}
		if(italOpen){
			output.append("</i>\n");
			italOpen = false;
		}
		if(underOpen){
			output.append("</u>");
			underOpen = false;
		}
		if(strikeOpen){
			output.append("</del>\n");
			strikeOpen = false;
		}
		output.append("\n");
	}

}
